package com.ck.framework.web.service;

import cn.hutool.core.util.ObjectUtil;
import com.ck.common.constant.AjaxResult;
import com.ck.common.constant.CacheConstants;
import com.ck.common.constant.Constants;
import com.ck.common.constant.UserConstants;
import com.ck.common.entity.SysLogininfor;
import com.ck.common.entity.SysUser;
import com.ck.common.exception.BusinessException;
import com.ck.common.exception.user.CaptchaException;
import com.ck.common.exception.user.CaptchaExpireException;
import com.ck.common.exception.user.UserPasswordNotMatchException;
import com.ck.common.prop.BlackIpListProp;
import com.ck.common.utils.MessageUtils;
import com.ck.common.utils.ip.IpUtils;
import com.ck.common.utils.jwt.JwtUtil;
import com.ck.common.utils.jwt.RedisCache;
import com.ck.common.vo.LoginInfoVo;
import com.ck.common.vo.LoginUserVo;
import com.ck.framework.security.threadlocal.AuthenticationContextHolder;
import com.ck.framework.manager.AsyncManager;
import com.ck.framework.manager.factory.AsyncFactory;
import com.ck.system.mapper.SysLogininforMapper;
import com.ck.system.service.SysConfigService;
import com.ck.system.service.SysUserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import java.util.HashMap;


/**
 * @author ck
 * @date 2023/3/23 10:09
 * desc:登录校验方法
 */
@Component
@Slf4j
public class SysLoginService {

    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private RedisCache redisCache;

    @Autowired
    private SysUserService userService;

    @Autowired
    private TokenService tokenService;

    @Autowired
    private BlackIpListProp blackIpListProp;

    @Autowired
    private SysLogininforMapper sysLogininforMapper;

    @Autowired
    private SysConfigService configService;



    /**
     * 用户登录 Captcha
     * @param user
     * @return
     */
    public AjaxResult loginForCaptcha(LoginInfoVo user) {

        //登录前置参数校验
        loginPreCheckForCaptcha(user);

        //图形验证码校验
        validateCaptcha(user.getUsername(),user.getCode(), user.getUuid());

        //身份验证
        Authentication authenticate = null;
        try {
            //用户验证
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
            //用户名、密码存入threadLocal中
            AuthenticationContextHolder.setContext(authenticationToken);
            //该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authenticate = authenticationManager.authenticate(authenticationToken);
        } catch (Exception e) {
            if (e instanceof BadCredentialsException){
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(user.getUsername(), Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                throw new UserPasswordNotMatchException();
            }
            else {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(user.getUsername(), Constants.LOGIN_FAIL, e.getMessage()));
                throw new BusinessException(e.getMessage());
            }
        } finally {
            //清除ThreadLocal，防止内存溢出
            AuthenticationContextHolder.clearContext();
        }

        //异步记录登录信息
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(user.getUsername(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
//        //多租户开启情况下 日志记录只能是同步的，不能设置成异步日志，会获取不到租户id
//        recordLoginInfo(user.getUsername(), Constants.LOGIN_SUCCESS,"登录成功");

        //获得用户信息，即UserDetails对象

        LoginUserVo loginUser = (LoginUserVo) authenticate.getPrincipal();
        //认证通过生成token
        String token = tokenService.createToken(loginUser);
        return AjaxResult.success().put("token",token);
    }




    /**
     * 用户登录 Sms
     * @param user
     * @return
     */
    public AjaxResult loginForSms(LoginInfoVo user) {

        //登录前置参数校验
        loginPreCheckForSms(user);

        //手机号验证码校验
        validateSmsCode(user.getPhone(),user.getCode());

        SysUser sysUser = userService.queryUserByPhone(user.getPhone());

        //如果库中没有该号码则注册该号码
        if (ObjectUtil.isEmpty(sysUser)) {
            //需要注册该用户
            sysUser = new SysUser();
            sysUser.setPhonenumber(user.getPhone());
            sysUser.setPassword(DigestUtils.md5Hex("123456"));
            sysUser.setUserName(user.getPhone());
            this.userService.save(sysUser);
        }

        //根据phone生成token
        String token = JwtUtil.createJWT(user.getPhone());

        //token存入redis
        redisCache.setCacheObject(CacheConstants.LOGIN_TOKEN_KEY+user.getPhone(),sysUser);

        //token响应给前端
        HashMap<String, String> map = new HashMap<>();
        map.put("token",token);
        return new AjaxResult(200,"登陆成功",map);
    }




    /**
     * 图形验证码登录参数前置校验
     * @param user
     */
    private void loginPreCheckForCaptcha(LoginInfoVo user) {
        // 用户名或密码为空 错误
        if (StringUtils.isEmpty(user.getUsername()) || StringUtils.isEmpty(user.getPassword()))
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(user.getUsername(), Constants.LOGIN_FAIL, MessageUtils.message("not.null")));
            throw new BusinessException("用户不存在");
        }
        //uuid为空 错误
        if (StringUtils.isEmpty(user.getUuid())){
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(user.getUsername(), Constants.LOGIN_FAIL, MessageUtils.message("not.null")));
            throw new BusinessException("uuid不能为空");
        }
        //code为空 错误
        if (StringUtils.isEmpty(user.getCode())){
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(user.getUsername(), Constants.LOGIN_FAIL, MessageUtils.message("not.null")));
            throw new BusinessException("code不能为空");
        }
        // 密码如果不在指定范围内 错误
        if (user.getPassword().length() < UserConstants.PASSWORD_MIN_LENGTH
                || user.getPassword().length() > UserConstants.PASSWORD_MAX_LENGTH)
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(user.getUsername(), Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
            throw new UserPasswordNotMatchException();
        }
        // 用户名不在指定范围内 错误
        if (user.getUsername().length() < UserConstants.USERNAME_MIN_LENGTH
                || user.getUsername().length() > UserConstants.USERNAME_MAX_LENGTH)
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(user.getUsername(), Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
            throw new UserPasswordNotMatchException();
        }
        // IP名单校验
        String blackStr = blackIpListProp.getBlackIPList();
        if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr()))
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(user.getUsername(), Constants.LOGIN_FAIL, MessageUtils.message("login.blocked")));
            throw new BusinessException("该IP被列入黑名单");
        }

    }


    /**
     * 手机验证码登录前置校验
     * @param user
     */
    private void loginPreCheckForSms(LoginInfoVo user) {
        if (StringUtils.isEmpty(user.getPhone())){
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(user.getPhone(),Constants.LOGIN_FAIL,MessageUtils.message("not.null")));
            throw new BusinessException("手机号不能为空");
        }
        if (StringUtils.isEmpty(user.getCode())){
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(user.getPhone(),Constants.LOGIN_FAIL,MessageUtils.message("not.null")));
            throw new BusinessException("验证码不能为空");
        }
    }



    /**
     * 图形验证码校验
     * @param code
     * @param uuid
     */

    private void validateCaptcha(String userName,String code, String uuid) {
        boolean captchaEnabled = configService.selectCaptchaEnabled();
        if (captchaEnabled){
            //拼接key
            String verifyKey=CacheConstants.CAPTCHA_CODE_KEY+ uuid;
            String captcha = redisCache.getCacheObject(verifyKey);
            //删除key
            redisCache.deleteObject(verifyKey);
            if (StringUtils.isEmpty(captcha)){
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName,Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
                throw new CaptchaExpireException();
            }
            //校验
            if (!code.equalsIgnoreCase(captcha)){
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName,Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
                throw new CaptchaException();
            }
        }
    }

    /**
     * 手机号验证码校验
     * @param phone
     * @param code
     */
    private void validateSmsCode(String phone, String code) {
        //拼接key
        String verifyKey=CacheConstants.SMS_CODE_KEY+ phone;
        String smsCode = redisCache.getCacheObject(verifyKey);
        //删除key
        redisCache.deleteObject(verifyKey);

        if (StringUtils.isEmpty(smsCode)){
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(smsCode,Constants.LOGIN_FAIL, MessageUtils.message("user.sms.expire")));
            throw new BusinessException("验证码已过期");
        }
        if (!StringUtils.equals(code,smsCode)){
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(smsCode,Constants.LOGIN_FAIL, MessageUtils.message("user.sms.error")));
            throw new BusinessException("验证码错误");
        }

    }

    /**
     * 记录登录信息
     * @param userName
     */
    private void recordLoginInfo(String userName,String status,String message) {
        SysLogininfor loginInfo = new SysLogininfor();
        loginInfo.setUserName(userName);
        loginInfo.setIpaddr(IpUtils.getIpAddr());
        loginInfo.setMsg(message);
        //设置日志状态
        if (StringUtils.equalsAny(status,Constants.LOGIN_SUCCESS,Constants.LOGOUT,Constants.REGISTER)){
            loginInfo.setStatus(Constants.SUCCESS);
        }
        else if(Constants.LOGIN_FAIL.equals(status)){
            loginInfo.setStatus(Constants.FAIL);
        }
        sysLogininforMapper.insertLogininfor(loginInfo);
    }



    /**
     * 用户登出
     * @return
     */
    public AjaxResult logout() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        LoginUserVo loginUser = (LoginUserVo) authentication.getPrincipal();
        Long userid = loginUser.getUser().getUserId();
        redisCache.deleteObject(CacheConstants.LOGIN_TOKEN_KEY+userid);
        return new AjaxResult(200,"登出成功");
    }


}
